﻿using System;
using System.Globalization;
using JTLeigh.Commerce.Spencer.Core.Common.Exceptions;

namespace JTLeigh.Commerce.Spencer.Core.Common
{

	/// <summary>
	/// <see langword="static" /> methods for ensuring code is safe and fails fast.
	/// </summary>
	public static class Safety
	{

		#region Static Methods

		/// <summary>
		/// Asserts that the specified argument is not <see langword="null"/>.
		/// </summary>
		/// <param name="argumentName">The name of the argument.</param>
		/// <param name="argumentValue">The value of the argument.</param>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="argumentName"/> is
		/// <see langword="null"/>.</exception>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="argumentValue"/> 
		/// is <see langword="null"/>.</exception>
		public static void AssertArgumentNotNull(string argumentName, object argumentValue)
		{
			if (argumentName == null)
			{
				throw new ArgumentNullException("argumentName", "argumentName must not be null.");
			}
			if (argumentValue == null)
			{
				throw new AssertionFailedException(String.Format(CultureInfo.InvariantCulture, "{0} must not be null.", argumentName));
			}
		}

		/// <summary>
		/// Asserts that the specified value is not <see langword="null"/>.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="message">The message explaining the failed assertion.</param>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="value"/> is
		/// <see langword="null"/>.</exception>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is
		/// <see langword="null"/>.</exception>
		public static void AssertNotNull(object value, string message)
		{
			if (message == null)
			{
				throw new ArgumentNullException("message", "message must not be null.");
			}
			if (value == null)
			{
				throw new AssertionFailedException(message);
			}
		}

		/// <summary>
		/// Asserts that the specified value is <see langword="true"/>.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="message">The message explaining the failed assertion.</param>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="value"/> is
		/// <see langword="false"/>.</exception>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is
		/// <see langword="null"/>.</exception>
		public static void AssertTrue(bool value, string message)
		{
			if (message == null)
			{
				throw new ArgumentNullException("message", "message must not be null.");
			}
			if (!value)
			{
				throw new AssertionFailedException(message);
			}
		}

		/// <summary>
		/// Asserts that the specified value is <see langword="false"/>.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="message">The message explaining the failed assertion.</param>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="value"/> is
		/// <see langword="false"/>.</exception>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is
		/// <see langword="null"/>.</exception>
		public static void AssertFalse(bool value, string message)
		{
			if (message == null)
			{
				throw new ArgumentNullException("message", "message must not be null.");
			}
			if (value)
			{
				throw new AssertionFailedException(message);
			}
		}

		/// <summary>
		/// Asserts that the specified value is equal to an expected value.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="expected">The expected value.</param>
		/// <param name="message">The message explaining the failed assertion.</param>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="value"/> is
		/// not equal to <paramref name="expected"/>.</exception>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is
		/// <see langword="null"/>.</exception>
		public static void AssertEqual(object value, object expected, string message)
		{
			if (message == null)
			{
				throw new ArgumentNullException("message", "message must not be null.");
			}
			if (value == null)
			{
				if (expected != null)
				{
					throw new AssertionFailedException(String.Format(CultureInfo.InvariantCulture, "{0} (Expected '{1}' but got 'null' instead.)", message, expected));
				}
			}
			else
			{
				if (!value.Equals(expected))
				{
					throw new AssertionFailedException(String.Format(CultureInfo.InvariantCulture, "{0} (Expected '{1}' but got '{2}' instead.)", message, (expected ?? "null"), value));
				}
			}
		}

		/// <summary>
		/// Asserts that the specified value is within a range of values.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="expectedLowerBound">The expected lower bound.</param>
		/// <param name="expectedUpperBound">The expected upper bound.</param>
		/// <param name="message">The message explaining the failed assertion.</param>
		/// <exception cref="AssertionFailedException">Thrown if <paramref name="value"/> is
		/// less than <paramref name="expectedLowerBound"/> or greater than 
		/// <paramref name="expectedUpperBound"/>.</exception>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is
		/// <see langword="null"/>.</exception>
		/// <remarks>
		/// <para>
		/// This function is called in several places where <paramref name="value"/> is not 
		/// known to be <see cref="IComparable"/>.
		/// </para>
		/// </remarks>
		public static void AssertInRange(object value, IComparable expectedLowerBound, IComparable expectedUpperBound, string message)
		{
			if (expectedLowerBound == null)
			{
				throw new ArgumentNullException("expectedLowerBound", "expectedLowerBound must not be null.");
			}
			if (expectedUpperBound == null)
			{
				throw new ArgumentNullException("expectedUpperBound", "expectedUpperBound must not be null.");
			}
			if (message == null)
			{
				throw new ArgumentNullException("message", "message must not be null.");
			}
			if (value == null)
			{
				throw new AssertionFailedException(String.Format(CultureInfo.InvariantCulture, "{0} (Expected between '{1}' and '{2}', but got 'null' instead.)", message, expectedLowerBound, expectedUpperBound));
			}
			if ((expectedLowerBound.CompareTo(value) > 0) || (expectedUpperBound.CompareTo(value) < 0))
			{
				throw new AssertionFailedException(String.Format(CultureInfo.InvariantCulture, "{0} (Expected between '{1}' and '{2}', but got '{3}' instead.)", message, expectedLowerBound, expectedUpperBound, value));
			}
		}

		#endregion // Static Methods

	}

}
